package com.kob.backend.consumer.utils;

import com.alibaba.fastjson2.JSONObject;
import com.kob.backend.consumer.WebSocketServer;
import com.kob.backend.pojo.Bot;
import com.kob.backend.pojo.Record;
import com.kob.backend.pojo.User;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;

public class Game extends Thread {//只要继承了这个类，就是多线程
    final private Integer rows;
    final private Integer cols;
    final private Integer inner_walls_count;
    final private int[][] g;
    final private static int[] dx = {-1, 0, 1, 0}, dy = {0, 1, 0, -1};
    private String status = "playing";//存储游戏进度状态：playing(还未分出胜负) -> finished(结束了)
    private String loser = "";//存谁输了 all(平局)A(A输了)B(B输了)
    private Integer nextStepA = null;//存储两名玩家的下一步操作，方便多线程使用，null表示没有获取到下一步操作
    private Integer nextStepB = null;//0123表示上下左右四个方向
    private ReentrantLock lock = new ReentrantLock();//为多线程读写变量加锁
    private final static String addBotUrl = "http://127.0.0.1:3002/bot/add/";//发给botrunningsystem的url
    public void setNextStepA(Integer nextStepA) {//方便外部线程写入
        lock.lock();
        try {
            this.nextStepA = nextStepA;//把写入放在锁里面
        }finally {
            lock.unlock();//这样即使报错，也能解锁
        }
    }
    public void setNextStepB(Integer nextStepB) {
        lock.lock();
        try {
            this.nextStepB = nextStepB;
        }finally {
            lock.unlock();//这样即使报错，也能解锁
        }
    }
    final private Player playerA,playerB;//每张地图存两个玩家作为参数
    public Game(Integer rows, Integer cols, Integer inner_walls_count, Integer idA, Bot bota, Integer idB, Bot botb) {//传入两个玩家的id填充构造函数

        this.rows = rows;
        this.cols = cols;
        this.inner_walls_count = inner_walls_count;
        this.g = new int[rows][cols];

        Integer botIdA=-1,botIdB=-1;//默认AB都是亲自出马
        String botCodeA="",botCodeB="";
        if(bota!=null){//bot出马，就取出id和代码
            botIdA=bota.getId();
            botCodeA=bota.getContent();
        }
        if(botb!=null){//bot出马，就取出id和代码
            botIdB=botb.getId();
            botCodeB=botb.getContent();
        }
        playerA = new Player(idA,botIdA,botCodeA, rows - 2, 1, new ArrayList<>());//初始化A玩家，坐标左下角，路径是空
        playerB = new Player(idB, botIdB,botCodeB,1, cols - 2, new ArrayList<>());//把bot也传给player
    }

    public Player getPlayerA() {
        return playerA;
    }
    public Player getPlayerB() {
        return playerB;
    }

    public int[][] getG() {//输出地图数组
        return g;
    }

    private boolean check_connectivity(int sx, int sy, int tx, int ty) {//判断连通性
        if (sx == tx && sy == ty) return true;
        g[sx][sy] = 1;

        for (int i = 0; i < 4; i ++ ) {
            int x = sx + dx[i], y = sy + dy[i];
            if (x >= 0 && x < this.rows && y >= 0 && y < this.cols && g[x][y] == 0) {
                if (check_connectivity(x, y, tx, ty)) {
                    g[sx][sy] = 0;//恢复现场
                    return true;
                }
            }
        }
        g[sx][sy] = 0;//恢复现场
        return false;
    }

    private boolean draw() {  // 画地图
        for (int i = 0; i < this.rows; i ++ ) {
            for (int j = 0; j < this.cols; j ++ ) {
                g[i][j] = 0;//初始化，没墙
            }
        }
        //四周加上墙
        for (int r = 0; r < this.rows; r ++ ) {
            g[r][0] = g[r][this.cols - 1] = 1;
        }
        for (int c = 0; c < this.cols; c ++ ) {
            g[0][c] = g[this.rows - 1][c] = 1;
        }

        Random random = new Random();
        for (int i = 0; i < this.inner_walls_count / 2; i ++ ) {//中心对称加墙
            for (int j = 0; j < 1000; j ++ ) {
                int r = random.nextInt(this.rows);//生成0~rows-1之间的随机整数
                int c = random.nextInt(this.cols);

                if (g[r][c] == 1 || g[this.rows - 1 - r][this.cols - 1 - c] == 1)//已经有墙
                    continue;
                if (r == this.rows - 2 && c == 1 || r == 1 && c == this.cols - 2)//起点或终点不能放墙
                    continue;

                g[r][c] = g[this.rows - 1 - r][this.cols - 1 - c] = 1;//中心对称处理
                break;
            }
        }

        return check_connectivity(this.rows - 2, 1, 1, this.cols - 2);//检查起点到终点的连通性，判断地图是否合法
    }

    public void createMap() {
        for (int i = 0; i < 1000; i ++ ) {
            if (draw())//地图如果不合法，就一直重复画
                break;
        }
    }
    private String getInput(Player player) {  // 将当前的局面信息，编码成字符串
        Player me, you;
        if (playerA.getId().equals(player.getId())) {//辨别你我
            me = playerA;
            you = playerB;
        } else {
            me = playerB;
            you = playerA;
        }//返回具体对局信息
        return getMapString() + "#" +
                me.getSx() + "#" +
                me.getSy() + "#(" +
                me.getStepsString() + ")#" +
                you.getSx() + "#" +
                you.getSy() + "#(" +
                you.getStepsString() + ")";
    }
    private void sendBotCode(Player player) {//判断是否要执行代码，并把代码发给botrunningsystem
        if (player.getBotId().equals(-1)) return;  // 亲自出马，不需要执行代码
        MultiValueMap<String, String> data = new LinkedMultiValueMap<>();//必须是这个哈希表
        data.add("user_id", player.getId().toString());//传递的参数
        data.add("bot_code", player.getBotCode());
        data.add("input", getInput(player));//这是一个辅助函数
        WebSocketServer.restTemplate.postForObject(addBotUrl, data, String.class);//向botrunningsystem发送信息
    }


    private boolean nextStep() {  // 等待两名玩家的下一步操作（最长可以等5.2s，读到输入会提前终止）
        try {
            Thread.sleep(200);//看前端代码可知，蛇1秒走五格，0.2秒走一格，为了防止玩家按键太多太快导致覆盖了一些按钮操作，就先睡0.2s
        } catch (InterruptedException e) {//此时两秒内如果输入多个方向，那就只会读取最后一次
            throw new RuntimeException(e);
        }

        sendBotCode(playerA);//AB都可能是bot，所以都要调用这个函数
        sendBotCode(playerB);

        for (int i = 0; i < 50; i ++ ) {//等待五秒钟（每次5s内的操作都合法），分50次，每次0.1秒
            try {
                Thread.sleep(100);//先睡0.1秒，然后判断有没有读到两名玩家的操作，读到就返回true
                lock.lock();//注意睡眠要放在锁外面，不然容易占着茅坑不拉屎，
                try {
                    if (nextStepA != null && nextStepB != null) {//两名玩家都输入完毕
                        playerA.getSteps().add(nextStepA);//记录操作的方向
                        playerB.getSteps().add(nextStepB);
                        return true;
                    }
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    private boolean check_valid(List<Cell> cellsA, List<Cell> cellsB) {//判断A蛇是不是死了
        int n = cellsA.size();
        Cell cell = cellsA.get(n - 1);//取出蛇头
        if (g[cell.x][cell.y] == 1) return false;//A蛇头有没有撞墙

        for (int i = 0; i < n - 1; i ++ ) {//A蛇头有没有碰到自己的身子
            if (cellsA.get(i).x == cell.x && cellsA.get(i).y == cell.y)
                return false;
        }
        for (int i = 0; i < n - 1; i ++ ) {//A蛇头有没有碰到B的身子
            if (cellsB.get(i).x == cell.x && cellsB.get(i).y == cell.y)
                return false;
        }
        return true;
    }

    private void judge() {  // 判断两名玩家下一步操作是否合法
        List<Cell> cellsA = playerA.getCells();//先把两条蛇取出来
        List<Cell> cellsB = playerB.getCells();
        boolean validA = check_valid(cellsA, cellsB);//判断A蛇死没
        boolean validB = check_valid(cellsB, cellsA);//判断B蛇死没
        if (!validA || !validB) {//有一条死了，就结束了
            status = "finished";

            if (!validA && !validB) {//都死，平局
                loser = "all";
            } else if (!validA) {
                loser = "A";
            } else {
                loser = "B";
            }
        }
    }

    private void sendAllMessage(String message) {//向每一名玩家广播信息
        if( WebSocketServer.users.get(playerA.getId())!=null)
            WebSocketServer.users.get(playerA.getId()).sendMessage(message);//拿WebSocketServer类的静态哈希表，获取链接发送信息
        if( WebSocketServer.users.get(playerB.getId())!=null)
            WebSocketServer.users.get(playerB.getId()).sendMessage(message);
    }

    private void sendMove() {  // 向两个Client传递移动信息
        lock.lock();//涉及多线程对nextStep的读操作，要加锁
        try {
            JSONObject resp = new JSONObject();
            resp.put("event", "move");//确定信息类型
            resp.put("a_direction", nextStepA);//传ab移动方向
            resp.put("b_direction", nextStepB);
            sendAllMessage(resp.toJSONString());
            nextStepA = nextStepB = null;//下一步操作前，把操作清空
        } finally {
            lock.unlock();
        }
    }

    private String getMapString() {
        StringBuilder res = new StringBuilder();
        for (int i = 0; i < rows; i ++ ) {
            for (int j = 0; j < cols; j ++ ) {
                res.append(g[i][j]);
            }
        }
        return res.toString();
    }

    private void updateUserRating(Player player, Integer rating) {//在提交对局记录的时候，顺便更新一下天梯分数
        User user = WebSocketServer.userMapper.selectById(player.getId());//这里userMapper是静态public
        user.setRating(rating);
        WebSocketServer.userMapper.updateById(user);
    }


    private void saveToDatabase() {//游戏结束时调用此函数，把对局记录传入数据库
        Integer ratingA = WebSocketServer.userMapper.selectById(playerA.getId()).getRating();//提交前，计算一下天体积分
        Integer ratingB = WebSocketServer.userMapper.selectById(playerB.getId()).getRating();
        if ("A".equals(loser)) {//计算加减分
            ratingA -= 2;
            ratingB += 5;
        } else if ("B".equals(loser)) {
            ratingA += 5;
            ratingB -= 2;
        }
        updateUserRating(playerA, ratingA);//更新分数
        updateUserRating(playerB, ratingB);

        Record record = new Record(
                null,//自增，不用传
                playerA.getId(),
                playerA.getSx(),
                playerA.getSy(),
                playerB.getId(),
                playerB.getSx(),
                playerB.getSy(),
                playerA.getStepsString(),
                playerB.getStepsString(),
                getMapString(),
                loser,
                new Date()
        );
        WebSocketServer.recordMapper.insert(record);
    }

    private void sendResult() {  // 向两个Client公布结果
        JSONObject resp = new JSONObject();
        resp.put("event", "result");//表明信息类型
        resp.put("loser", loser);
        saveToDatabase();//调用对战记录存数据库的函数
        sendAllMessage(resp.toJSONString());//调用广播函数
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i ++ ) {//格子13*14，所以蛇1000步之内，一定能结束游戏
            if (nextStep()) {  // 是否获取了两条蛇的下一步操作（因为这个函数里面等了5s，5s内读不到，就会游戏结束）
                judge();//判断操作合法性
                if (status.equals("playing")) {
                    sendMove();//没有结束，就向前端两个玩家广播方向操作
                } else {
                    sendResult();//操作不合法，游戏结束，向两名玩家广播结果
                    break;
                }
            } else {
                status = "finished";
                lock.lock();//涉及多线程nextStep的读操作，所以要加锁
                try {//从输入状态判断谁输了
                    if (nextStepA == null && nextStepB == null) {
                        loser = "all";
                    } else if (nextStepA == null) {
                        loser = "A";
                    } else {
                        loser = "B";
                    }
                } finally {
                    lock.unlock();
                }
                sendResult();//游戏结束，广播结果
                break;
            }
        }
    }

}
